/*
  Conexant AccessRunner init process
  Copyright (C) 2003 Josep Comas

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Author     : Josep Comas <jcomas@gna.es>
  Creation   : 24/1/2003

  Description: This program inits Conexant AccessRunner (USB ADSL Modem).

  Modems:

  User: Josep Comas <jcomas@gna.es>
  Manufacturer: Mac System (http://www.macsysco.com/)
  Model: MA08CU (ADSL 100U modem)
  Distribuited by: Vitelcom
  Model: EPS 5002 USB
  Internet provider: Telefonica
  Country: Spain
  Protocols: RFC1483/2684 routed, PPPoE

  User: Pascal Boucher <bcrp17@tiscali.fr>
  Manufacturer: Olitec (http://www.olitec.com/)
  Model: Modem USB ADSL V3
  Internet provider: Tiscali
  Country: France
  Protocol: PPPoATM

  User: Thomas Mikosch
  Manufacturer: Amigo Technology Co. (http://www.amigo.com.tw)
  Model: AMX-CA86U
  Distribuited by: Trust (http://www.trust.com) 
  Model: 235A Speedlink ADSL Web Modem (item no. 13141)
  Internet provider: HetNet
  Country: The Netherlands
  Protocol: PPPoATM

  User: Pawel Konieczny <konieczp@users.sourceforge.net>
  Manufacturer: Amigo Technology Co. (http://www.amigo.com.tw)
  Model: AMX-CA80U-4
  Distributed by (OEM): E-Tech (http://www.e-tech.nu/zon)
  Model: E-Tech ADSL USB Modem V2
  Internet provider: Zon (http://www.zonnet.nl)
  ADSL provider: Versatel (http://www.versatel.nl)
  Country: The Netherlands
  Protocol: RFC1483/2684 bridged/routed

  Log:

  28/4/2003 Josep Comas
  Added Olitec Modem USB ADSL V3 changes from Pascal Boucher
  Added support for Amigo AMX-CA86U

  30/4/2003 Josep Comas
  Added support for Olitec Modem USB ADSL V2

  1/5/2003 Josep Comas
  Added dispatch routine to retrieve info for commands sent
  Retrieve firmware version
  Retrieve MAC address
  Retrieve line and ADSL status
  Retrieve downbitrate and upbitrate

  2/5/2003 Josep Comas
  Better retrieving info (sleeps added)

  4/5/2003 Pawel Konieczny
  Added support for E-Tech Modem V2

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <usb.h>
#include "usbi.h"
#include <libintl.h>
#include <locale.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

/* uncomment following line to print debug info or define in compiler parameters: */
//#define DEBUG 1 
/* uncomment following line to print debug transfers or define in compiler parameters: */
/* #define DEBUG_TRANSFER 1 */


#ifdef DEBUG
#define PDEBUG(arg...)  printf(arg)
#else
#define PDEBUG(arg...)
#endif

/* FCLK/BCLK */
#define PLL_FAST_144MHZ_RM 0x00000005
#define PLL_FAST_100MHZ_RM 0x00000003
#define PLL_FAST_144MHZ 0x02D874DF  /* Divide by 3, F=144Mhz, U=72Mhz */
#define PLL_FAST_100MHZ 0x0196a51a  /* Divide by 4, B=100Mhz, P=50Mhz */

#if defined( AMXCA86U )

  /* modem usb identifications */
  #define ID_VENDOR    0x0eb0  /* Vendor = Amigo Technology Co. */
  #define ID_PRODUCT1  0x3457  /* Product = AMX-CA86U */
  /* ARM940T registers values */
  #define ARM_PLL_F_VALUE PLL_FAST_144MHZ  /* FCLK PLL Register value */
  #define ARM_PLL_B_VALUE PLL_FAST_100MHZ  /* BCLK PLL Register value */

#elif defined( OLITECV2 )

  /* modem usb identifications */
  #define ID_VENDOR    0x08e3  /* Vendor  = Olitec */
  #define ID_PRODUCT1  0x0100  /* Product = ADSL modem version 2 */
  /* ARM940T registers values */
  #define ARM_PLL_F_VALUE PLL_FAST_144MHZ  /* FCLK PLL Register value */
  #define ARM_PLL_B_VALUE PLL_FAST_100MHZ  /* BCLK PLL Register value */
  /* initial firmware bytes */
  #define FIRMBYTE1 0x1c
  #define FIRMBYTE2 0x24
  #define FIRMBYTE3 0x9f
  #define FIRMBYTE4 0xe5
  #define FIRMBYTE5 0x00

#elif defined( OLITECV3 )

  /* modem usb identifications */
  #define ID_VENDOR    0x08e3  /* Vendor  = Olitec */
  #define ID_PRODUCT1  0x0102  /* Product = ADSL modem version 3 */
  /* ARM940T registers values */
  #define ARM_PLL_F_VALUE PLL_FAST_144MHZ_RM  /* FCLK PLL Register value */
  #define ARM_PLL_B_VALUE PLL_FAST_100MHZ_RM  /* BCLK PLL Register value */
  /* initial firmware bytes */
  #define FIRMBYTE1 0x1c
  #define FIRMBYTE2 0x24
  #define FIRMBYTE3 0x9f
  #define FIRMBYTE4 0xe5
  #define FIRMBYTE5 0x00

#elif defined( ETECHV2 )

  /* modem usb identifications */
  #define ID_VENDOR    0x0572  /* Vendor = Conexant */
  #define ID_PRODUCT1  0xcb00  /* Product = ADSL modem Hasbani */
  /* ARM940T registers values */
  #define ARM_PLL_F_VALUE PLL_FAST_144MHZ_RM  /* FCLK PLL Register value */
  #define ARM_PLL_B_VALUE PLL_FAST_100MHZ_RM  /* BCLK PLL Register value */
  /* initial firmware bytes */
  #define FIRMBYTE3 0x9f
  #define FIRMBYTE4 0xe5
  #define FIRMBYTE5 0x00

#else

  /* modem usb identifications */
  #define ID_VENDOR    0x0572  /* Vendor = Conexant */
  #define ID_PRODUCT1  0xcafe  /* Product = ADSL modem Euphrates */
  /* ARM940T registers values */
  #define ARM_PLL_F_VALUE PLL_FAST_144MHZ  /* FCLK PLL Register value */
  #define ARM_PLL_B_VALUE PLL_FAST_100MHZ  /* BCLK PLL Register value */
  /* initial firmware bytes */
  #define FIRMBYTE1 0x28
  #define FIRMBYTE2 0x23
  #define FIRMBYTE3 0x9f
  #define FIRMBYTE4 0xe5
  #define FIRMBYTE5 0x00

#endif

/* ARM940T registers */
#define ARM_PLL_F    0x00350068  /* FCLK PLL Register */
#define ARM_PLL_B    0x0035006c  /* BCLK PLL Register */
#define ARM_EMCR     0x00350010  /* External Memory Control Register */
#define SDRAM_ENABLE 0x00000001

/* P52 */
#define BR_ADDRESS       0x00180600
#define WB_SIGN_ADDRESS  0x00180500
#define BR_STACK_ADDRESS 0x00187f10

#define FW_ADDRESS       0x00801000
#define FS_ADDRESS       0x00eb0000

/* modem commands */
#define CMD_USB_CMD_ERR                 0x00
#define CMD_USB_GET_VER                 0x01
#define CMD_USB_READ_MEM                0X02
#define CMD_USB_WRITE_MEM               0x03
#define CMD_USB_RMW_MEM                 0x04
#define CMD_USB_CHECKSUM_MEM            0x05
#define CMD_USB_GOTO_MEM                0x06
#define CMD_CHIP_ADSL_LINE_START        0x84
#define CMD_CARD_INFO_GET               0x88
#define CMD_CARD_DATA_SET               0x8a
#define CMD_CARD_SERIAL_DATA_PATH_GET   0x8d
#define CMD_CARD_CONTROLLER_VERSION_GET 0x8f
#define CMD_CARD_GET_STATUS             0x90
#define CMD_CARD_GET_MAC_ADDRESS        0x91
#define CMD_CARD_GET_DATA_LINK_STATUS   0x92
#define CMD_CARD_PACKING_MODE           0x99

#define REPLY_CMD 1

/* line status */
#define LINE_STATUS_NOT_CONNECTED 1  /* line not established */
#define LINE_STATUS_CONNECTED     2  /* line connected */
#define LINE_STATUS_LOST          3  /* line connection lost */

/* adsl status */
/* a possible secuence: 0 -> 1 -> 7 -> 1 -> 2 -> 1 -> 2 -> 3 -> 5 */
#define ADSL_STATUS_DOWN                    0
#define ADSL_STATUS_ATTEMPTING_TO_ACTIVATE  1
#define ADSL_STATUS_TRAINING                2
#define ADSL_STATUS_CHANNEL_ANALYSIS        3
#define ADSL_STATUS_EXCHANGE                4
#define ADSL_STATUS_UP                      5

/* usb input and ouput address against modem */
#define USB_IN_INFO  0x81    /* IN endpoint address, receive info */
#define USB_OUT_INFO 0x01    /* OUT endpoint address, ask info, send firmware */
#define USB_IN_DATA  0x82    /* IN endpoint address, receive ATM cells */
#define USB_OUT_DATA 0x02    /* OUT endpoint address, send ATM cells */

/* timeouts & retries */
#define DATA_TIMEOUT 5000    /* Timeout (ms) for in/out data packets */ 
#define CTRL_TIMEOUT 1000    /* Timeout (ms) for in/out control packets */
#define TIMEOUT_ADD  1000    /* We get timeout then add additional time */
#define SEND_BULK_RETRIES 4  /* Max retries when you send a bulk packet */
#define READ_BULK_RETRIES 4  /* Max retries when you wait a bulk packet */
#define CTRL_MSG_RETRIES  4  /* Max retries when you transfer a control message */
#define MAX_WAIT_LINE_UP 90  /* Seconds to wait until ADSL line is up */

/* usb errors */
#define EPIPE      32        /* When we receive a STALL */
#define ETIMEDOUT 110        /* When we receive a NAK */

/* messages */
#define MA_BYTE  0  /* access type byte */
#define MA_WORD  1  /* access type word */
#define MA_DWORD 2  /* access type double word */
#define MAX_MESSAGE_SIZE 64       /* Maximum bytes of a message */
#define MAX_DATA_MESSAGE_SIZE 56  /* Maximum data bytes of a message */
#define MAX_DATA_TRANSFER 4096    /* Maximum bytes that we can transfer through USB bus */
#define SIZE_VALUE 4              /* Size of a value */

/* translation files */
#define TF_CODE "cxload"

#if !defined( OLITECV3 ) && !defined( ETECHV2 )
/* patch boot rom */
char inifirm[] = {
  0x78, 0x20, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3,
  0x00, 0x10, 0x82, 0xe5, 0x70, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3, 0x00, 0x10, 0x82, 0xe5,
  0x00, 0x10, 0xa0, 0xe3, 0x64, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5, 0x60, 0x20, 0x9f, 0xe5,
  0x00, 0x10, 0x82, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0x58, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5,

  0x00, 0x10, 0xa0, 0xe3, 0x50, 0x20, 0x9f, 0xe5,
  0x00, 0x10, 0x82, 0xe5, 0x4c, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5, 0x48, 0x20, 0x9f, 0xe5,
  0x00, 0x10, 0x82, 0xe5, 0x44, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5, 0x00, 0x10, 0xe0, 0xe3,
  0x3c, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5, 0x38, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5,

  0x34, 0x20, 0x9f, 0xe5, 0x00, 0x10, 0x82, 0xe5,
  0x30, 0x00, 0x9f, 0xe5, 0x00, 0xf0, 0xa0, 0xe1, 0x78, 0x00, 0x33, 0x00, 0x4c, 0x00, 0x35, 0x00,
  0x3c, 0x00, 0x33, 0x00, 0x44, 0x00, 0x33, 0x00, 0x2c, 0x00, 0x33, 0x00, 0x30, 0x00, 0x35, 0x00,
  0x34, 0x00, 0x35, 0x00, 0x38, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x44, 0x00, 0x35, 0x00,

  0x38, 0x00, 0x33, 0x00, 0x40, 0x00, 0x33, 0x00,
  0x00, 0x10, 0x80, 0x00};
#endif

struct cxvarvalue {
  char id;
  int value;
} CXVARVALUE;

/* params */

#ifdef AMXCA86U

  #define PARAM_FIRST 0x00
  #define PARAM_LAST  0x48
  struct cxvarvalue idsvar[] = {
    {0x03, 0x01},
    {0x04, 0x01},
    {0x07, 0x09},
    {0x08, 0x10},
    {0x0d, 0x02},
    {0x0f, 0xc8},
    {0x10, 0x01},
    {0x12, 0x22},
    {0x14, 0x01},
    {0x17, 0x0e},
    {0x18, 0x05},
    {0x2d, 0x80},
    {0x2f, 0x0a},
    {0x30, 0x0c},
    {0x31, 0x02},
    {0x32, 0x03},
    {0x36, 0x0180}
  };

#else
#ifdef OLITECV2

  #define PARAM_FIRST 0x00
  #define PARAM_LAST  0x41
  struct cxvarvalue idsvar[] = {
    {0x00, 0x30},
    {0x03, 0x01}, 
    {0x04, 0x01},
    {0x07, 0x09},
    {0x08, 0x10},
    {0x0c, 0x01},
    {0x0d, 0x02},
    {0x0f, 0xc8},
    {0x10, 0x01},
    {0x12, 0x22},
    {0x14, 0x01},
    {0x17, 0x0e},
    {0x18, 0x05},
    {0x2f, 0x14},
    {0x30, 0x0c},
    {0x31, 0x02},
    {0x32, 0x03}
  };

#else
#ifdef OLITECV3

  #define PARAM_FIRST 0x00
  #define PARAM_LAST  0x48
  struct cxvarvalue idsvar[] = {
    {0x03, 0x01},
    {0x04, 0x01},
    {0x07, 0x09},
    {0x08, 0x10},
    {0x0a, 0x04},
    {0x0c, 0x01},
    {0x0f, 0xc8},
    {0x10, 0x01},
    {0x14, 0x01},
    {0x17, 0x0e},
    {0x18, 0x05},
    {0x2d, 0x80},
    {0x2f, 0x0a},
    {0x30, 0x0c},
    {0x31, 0x02},
    {0x32, 0x03},
    {0x36, 0x0180}
  };

#else

  #define PARAM_FIRST 0x00
  #define PARAM_LAST  0x40
  struct cxvarvalue idsvar[] = {
    {0x00, 0x30},
    {0x03, 0x01}, 
    {0x04, 0x01},
    {0x07, 0x09},
    {0x08, 0x10},
    {0x0a, 0x03},
    {0x0c, 0x01},
    {0x0d, 0x02},
    {0x0f, 0xc8},
    {0x10, 0x01},
    {0x12, 0x39},
    {0x14, 0x01},
    {0x17, 0x0e},
    {0x18, 0x05},
    {0x1b, 0x0a},
    {0x1c, 0x01},
    {0x1d, 0x03},
    {0x1e, 0x0a},
    {0x2f, 0x14},
    {0x30, 0x0c},
    {0x31, 0x02},
    {0x32, 0x03}
  };

#endif
#endif
#endif

struct usb_modem_info {
  char firm_version[5];
  int down_bitrate;
  int up_bitrate;
  int line_status;
  int adsl_status;
  char mac[6];
};

struct usb_modem_info modem_info;


/* check if a file exists */
int file_exists(const char *filename)
{
  struct stat info_file;

  return stat(filename, &info_file) == 0;
}

/* show printable char */
void print_char(unsigned char c)
{
  if (c >= ' ' && c < 0x7f)
    printf("%c", c);
  else
    printf(".");
}

/* show buffer */
void dump(unsigned char *buf, int lenbuf, int lenline)
{
  int i, j;  /* counters */

  for (i = 0; i < lenbuf; i+= lenline)
  {
    for (j = i; j < lenbuf && j < i + lenline; j++)
      printf("%02x ", buf[j]);
    for (; j < i + lenline; j++)
      printf("   ");
    for (j = i; j < lenbuf && j < i + lenline; j++)
      print_char(buf[j]);
    printf("\n");
  }
}

/* transfer a control message to USB bus */
int transfer_ctrl_msg(usb_dev_handle *adsl_handle, int requesttype, int request, int value, int index, char *buf, int size)
{
  int j;  /* counter */
  int n;  /* bytes transfed or error code */
  int tmout = CTRL_TIMEOUT;  /* timeout value */

  n = 0;
  for (j = 0; j < CTRL_MSG_RETRIES; j++) {
    n = usb_control_msg(adsl_handle, requesttype, request, value, index,  buf, size, tmout);
    //n = size;
    if (n >= 0) {
#if DEBUG_TRANSFER
      printf(gettext("%d bytes transferred:\n"), n);
      dump(buf, n, 16);
#endif
      break;
    }
    else {
      printf(gettext("Error: usb_control_msg: %s\n"), usb_strerror());
      if (n == -EPIPE) {
        usb_clear_halt(adsl_handle, 0x00);
        usb_clear_halt(adsl_handle, 0x80);
      }
      else if (n == -ETIMEDOUT) {
	tmout += TIMEOUT_ADD;
      }
    }
  }
  if (n < 0) {
    printf(gettext("Error: usb_control_msg failed after %d retries\n"), CTRL_MSG_RETRIES);
    return -1;
  }
  return n;
}

/* receive a packet from USB bus by bulk transfer */
int read_bulk(usb_dev_handle *adsl_handle, int ep, char *buf, int size)
{
  int n;  /* bytes readed or error code */
  int i;  /* counter */
  int tmout = DATA_TIMEOUT;  /* timeout value */

  memset(buf, 0, sizeof(buf));
  n = 0;
  for (i = 0; i < READ_BULK_RETRIES; i++) {
    n = usb_bulk_read(adsl_handle, ep, buf, size, tmout);
    //n = size;
    if (n >= 0) {
#if DEBUG_TRANSFER
      printf(gettext("%d bytes downloaded:\n"), n);
      dump(buf, n, 16);
#endif
      break;
    }
    else {
      printf(gettext("Error: usb_bulk_read: %s\n"), usb_strerror());
      if (n == -EPIPE) {
        usb_clear_halt(adsl_handle, ep);
      }
      else if (n == -ETIMEDOUT) {
	tmout += TIMEOUT_ADD;
      }
    }
  }
  if (n < 0) {
    printf(gettext("Error: usb_bulk_read failed after %d retries\n"), READ_BULK_RETRIES);
    return -1;
  }
  return 0;
}

/* send one or more packets to USB bus by bulk transfer */
int send_bulk(usb_dev_handle *adsl_handle, int ep, char *buf, int nfil, int ncol)
{
  int i, j;  /* counters */
  int n;  /* bytes sent or error code */
  int tmout = DATA_TIMEOUT;  /* timeout value */

  n = 0;
  for (i = 0; i < nfil; i++)
  {
    for (j = 0; j < SEND_BULK_RETRIES; j++) {
      n = usb_bulk_write(adsl_handle, ep, buf+(i*ncol), ncol, tmout);
      //n = ncol;
      if (n >= 0) {
#if DEBUG_TRANSFER
        printf(gettext("%d bytes uploaded:\n"), n);
        dump(buf+(i*ncol), ncol, 16);
#endif
        break;
      }
      else {
        printf(gettext("Error: usb_bulk_write: %s\n"), usb_strerror());
	if (n == -EPIPE) {
	  usb_clear_halt(adsl_handle, ep);
	}
	else if (n == -ETIMEDOUT) {
	  tmout += TIMEOUT_ADD;
	}
      }
    }
    if (n < 0) {
      printf(gettext("Error: usb_bulk_write failed after %d retries\n"), SEND_BULK_RETRIES);
      return -1;
    }
  }
  return 0;
}

/* format a message */
void format_message(int cmd, int ldata, int atype, int address, char *bufin)
{
  char buf[8];  /* initial bytes of a message */

  memset(buf, 0, sizeof(buf));
  buf[0] = cmd & 0xff;    /* usb command */
  buf[1] = ldata & 0xff;  /* data lenght */ 
  buf[2] = atype & 0xff;  /* access type */
  buf[3] = 0x00;          /* ack request (0 = FALSE) */
  /* address */
  buf[4] = address & 0xff;
  buf[5] = (address >> 8) & 0xff;
  buf[6] = (address >> 16) & 0xff;
  buf[7] = (address >> 24) & 0xff;
  memcpy(bufin, buf, sizeof(buf));
}

/* write a value into an address */
int write_value(usb_dev_handle *adsl_handle, int address, int value)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  memset(buf, 0, sizeof(buf));
  buf[8] = value & 0xff;
  buf[9] = (value >> 8) & 0xff;
  buf[10] = (value >> 16) & 0xff;
  buf[11] = (value >> 24) & 0xff;
  format_message(CMD_USB_WRITE_MEM, 4, MA_DWORD, address, buf);
  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;

  return 0;
}

/* write a single byte into an address */
int write_byte(usb_dev_handle *adsl_handle, int address, char value)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  memset(buf, 0, sizeof(buf));
  buf[8] = value;
  format_message(CMD_USB_WRITE_MEM, 1, MA_BYTE, address, buf);
  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;

  return 0;
}

/* write a goto usb command */
int write_goto(usb_dev_handle *adsl_handle, int address)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */
  memset(buf, 0, sizeof(buf));

  format_message(CMD_USB_GOTO_MEM, 0, MA_BYTE, address, buf);
  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;

  return 0;
}

/* write all with zero values */
int write_zeros(usb_dev_handle *adsl_handle, char id)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  memset(buf, 0, MAX_MESSAGE_SIZE);
  buf[0] = id;

  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;
  return 0;
}

/* send a command with a value */
int command_value(usb_dev_handle *adsl_handle, char id, int value)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  memset(buf, 0, sizeof(buf));
  format_message(id, 0, 0, value, buf);
  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;

  return 0;
}

int write_data(usb_dev_handle *adsl_handle, char id, int data)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  memset(buf, 0, MAX_MESSAGE_SIZE);
  buf[0] = id;

  if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
    return -1;
  return 0;
}

/* find a idvar */
int find_var(char id, struct cxvarvalue *var, int lvar)
{
  int i;
  int pos = -1;

  for (i = 0; i < lvar; i++)
  {
    if (var[i].id == id) {
      pos = i;
      break;
    }
  }
  return pos;
}

/* write several values */
int write_values(usb_dev_handle *adsl_handle, char id, char first, char last)
{
  char buf[MAX_MESSAGE_SIZE];  /* message to send */

  int len = last - first + 1;  /* number of variables */
  int npackets = len/7;  /* number of packets to send */
  int i, j;  /* counters */
  char place = first;  /* address */
  char nvp; /* number of variables in a packet */
  int pos;  /* pos of idvar we are finding */

  if (len % 7 != 0)
    npackets++;

  PDEBUG(gettext("Packets to send = %i\n"), npackets);
  for (i = 0; i < npackets; i++)
  {
    memset(buf, 0, MAX_MESSAGE_SIZE);
    buf[0] = id;
    if ((i == npackets - 1) && (len % 7 != 0))
      nvp = len % 7;
    else
      nvp = 7;
    buf[4] = nvp;
    for (j = 0; j < nvp; j++)
    {
      buf[(8*j)+8] = place;
      pos = find_var(place, idsvar, sizeof(idsvar)/sizeof(struct cxvarvalue));
      if (pos >= 0) {
	buf[(8*j)+12] = idsvar[pos].value & 0xff;
        buf[(8*j)+13] = (idsvar[pos].value >> 8) & 0xff;
      }
      place++;
    }
    if (send_bulk(adsl_handle, USB_OUT_INFO, buf, 1, MAX_MESSAGE_SIZE))
      return -1;
  }
  return 0;

}

/* clear endpoints that we use */
void clear_endpoints(usb_dev_handle *adsl_handle, int step) {
  usb_resetep(adsl_handle, USB_IN_INFO);
  usb_resetep(adsl_handle, USB_OUT_INFO);
  usb_resetep(adsl_handle, USB_IN_DATA);
  usb_resetep(adsl_handle, USB_OUT_DATA);
  /*
  usb_clear_halt(adsl_handle, 0x00);
  usb_clear_halt(adsl_handle, 0x80);
  if (step >= 2) {
    usb_clear_halt(adsl_handle, USB_IN_INFO);
    usb_clear_halt(adsl_handle, USB_OUT_INFO);
    usb_clear_halt(adsl_handle, USB_IN_DATA);
    usb_clear_halt(adsl_handle, USB_OUT_DATA);
  }
  */
}

/* send_block */
int send_block(usb_dev_handle *adsl_handle, int place, char *bufin, int len)
{
  int npackets = len/MAX_DATA_MESSAGE_SIZE;  /* number of packets to send */
  int i;  /* counter */
  int lmes;  /* message length */
  int lblock = 0;  /* block length */
  char mes[MAX_MESSAGE_SIZE];  /* message to send */
  char block[MAX_DATA_TRANSFER];  /* block of messages to send */

  if (len % MAX_DATA_MESSAGE_SIZE != 0)
    npackets++;

  PDEBUG(gettext("Packets to send = %i\n"), npackets);
  for (i = 0; i < npackets; i++)
  {
     if (i == (npackets - 1) && (len % MAX_DATA_MESSAGE_SIZE != 0))
       lmes = len % MAX_DATA_MESSAGE_SIZE;
     else
       lmes = MAX_DATA_MESSAGE_SIZE;
     memset(mes, 0, MAX_MESSAGE_SIZE);
     memcpy(mes+8, bufin+(i*MAX_DATA_MESSAGE_SIZE), lmes);
     if (lmes % 4 != 0)  /* round up for MA_DWORD transfer */
       lmes = (lmes/4 + 1) * 4;
     format_message(CMD_USB_WRITE_MEM, lmes, MA_DWORD, place+(i*MAX_DATA_MESSAGE_SIZE), mes);
     memcpy(block+lblock, mes, MAX_MESSAGE_SIZE);
     lblock += MAX_MESSAGE_SIZE;
     if ((i == (npackets - 1)) || (lblock == MAX_DATA_TRANSFER)) {
       PDEBUG(gettext("Sending block...\n"));
       if (send_bulk(adsl_handle, USB_OUT_INFO, block, 1, lblock))
         return -1;
       lblock = 0;
     }
  }
  return 0;
}

/* return a value of an id */
int find_id_value(char id, char *buf, int *value)
{
  int i;  /* counter */
  int pos = -1;  /* where id was found */

  for (i = 0; i < buf[4]; i++)
  {
    if (buf[8+(i*8)] == id) {
      pos = i;
      *value = ((buf[15+(i*8)] & 0xff) << 24) | ((buf[14+(i*8)] & 0xff) << 16) | ((buf[13+(i*8)] & 0xff) << 8) | (buf[12+(i*8)] & 0xff);
      break;
    }
  }
  return pos;
}

/* dispatch received info */
void *dispatch_info(void *adsl_handle)
{
  int n;  /* bytes received */
  char buf[MAX_MESSAGE_SIZE];  /* where store message received */
  int value;  /* value of a id */

  memset(&modem_info, 0, sizeof(modem_info));
  strcpy(modem_info.firm_version, "n/a");
  for (;;) {
    memset(buf, 0, MAX_MESSAGE_SIZE);
    n = usb_bulk_read((usb_dev_handle *)adsl_handle, USB_IN_INFO, buf, MAX_MESSAGE_SIZE, DATA_TIMEOUT);
    if (n == MAX_MESSAGE_SIZE) 
    {
#if DEBUG_TRANSFER
      printf("Received message:\n");
      dump(buf, n, 16);
#endif
      if (buf[1] == REPLY_CMD) {
        switch (buf[0] & 0xff) {
          case CMD_CARD_CONTROLLER_VERSION_GET:
            memcpy(modem_info.firm_version, buf+4, sizeof(modem_info.firm_version));
            break; 
          case CMD_CARD_INFO_GET:
            //printf("Message:\n");
            //dump(buf, n, 16);
            if (find_id_value(0x00, buf, &value) != -1)
              modem_info.down_bitrate = value;
            if (find_id_value(0x01, buf, &value) != -1)
              modem_info.up_bitrate = value;
            if (find_id_value(0x02, buf, &value) != -1)
              modem_info.line_status = value;
            if (find_id_value(0x03, buf, &value) != -1)
              modem_info.adsl_status = value;
            if (find_id_value(0x05, buf, &value) != -1) {
              memcpy(&modem_info.mac, &value, sizeof(value));
              if (find_id_value(0x04, buf, &value) != -1)
                memcpy(&modem_info.mac[4], &value, 2);
            }
            break;
          case CMD_CARD_GET_STATUS:
            printf("Modem status = %d\n", buf[4]);
            break;
          case CMD_CARD_GET_DATA_LINK_STATUS:
            modem_info.line_status = *((int *)(buf+4));
            break;
          case CMD_CARD_PACKING_MODE:
#ifdef DEBUG
            printf("Packing mode reply:\n");
            dump(buf, n, 16);
#endif
            break;
          case CMD_CARD_GET_MAC_ADDRESS:
            memcpy(&modem_info.mac, buf + 4, 6);
            break;
        }
      }
    }
    sched_yield();
  }
}

/* load firmware */
int load_firmware(usb_dev_handle *adsl_handle, const char *fw_filename, const char *fs_filename)
{
  unsigned char buf[64*MAX_DATA_MESSAGE_SIZE];   /* max data that we can send */
  FILE *soft;   /* firmware file handle */
  FILE *fsys;   /* filesystem file handle */
  long len;     /* length */
  int place;    /* initial target address */
  pthread_t dispatch_info_thread;  /* thread to dispatch info received from commands sent */
  void *vt = NULL;  /* returned value from pthread_exit */
  int i;  /* counter */
  time_t first, last, before;  /* to wait */

  /* clear endpoints */
  clear_endpoints(adsl_handle, 2);

  /* open files */
  soft = fopen(fw_filename, "rb");
  if (soft == NULL)
  {
    printf(gettext("Error: I can't open file %s\n"), fw_filename);
    return -1;
  }
  fseek(soft, 0L, SEEK_END);
  len = ftell(soft);
  PDEBUG(gettext("Length of file %s = %ld bytes\n"), fw_filename, len);
  fseek(soft, 0L, SEEK_SET);
  len = fread(buf, 1, 6, soft);
  if (len <= 0)
  {
    printf(gettext("Error: No bytes to read from file %s\n"), fw_filename);
    return -1;
  }
  if (len != 6)
  {
    printf(gettext("Error: I can't read initial 6 bytes from file %s\n"), fw_filename);
    return -1;
  }
  /* check initial bytes */
  PDEBUG(gettext("Initial bytes from file %s:\n"), fw_filename);
#if DEBUG
  dump(buf, 6, 6);
#endif
  /* initial firmware bytes - works for all firmware */
  if (memcmp(buf + 2, "\x9f\xe5\x00\x10", 4) != 0) {
    printf(gettext("Error: Maybe file %s isn't Conexant firmware, contact with author of this program\n"), fw_filename);
    return -1;
  }

  if (fs_filename != NULL) 
  {
    fsys = fopen(fs_filename, "rb");
    if (fsys == NULL)
    {
      printf(gettext("Error: I can't open file %s\n"), fs_filename);
      return -1;
    }
    len = ftell(fsys);
    PDEBUG(gettext("Length of file %s = %ld bytes\n"), fs_filename, len);
    fseek(fsys, 0L, SEEK_SET);
    len = fread(buf, 1, 10, fsys);
    if (len < 10 || memcmp(buf + 5, "DOS12", 5) != 0) {
      printf(gettext("Error: Maybe file %s isn't filesystem image, contact with author of this data\n"), fs_filename);
      return -1;
    }
  }
  else
  {
    fsys = NULL;
  }

  /*******************************/
  /* initialize clock and memory */
  /*******************************/

  PDEBUG("PreInit...\n");
  if (write_value(adsl_handle, ARM_PLL_F, ARM_PLL_F_VALUE))
    return -1;
  if (write_value(adsl_handle, ARM_PLL_B, ARM_PLL_B_VALUE))
    return -1;
  if (write_value(adsl_handle, ARM_EMCR, SDRAM_ENABLE))
    return -1;
#ifdef ETECHV2
  if (write_byte(adsl_handle, 0x002d0000, 0x08))
    return -1;
#endif


  /*******************/
  /* send filesystem */
  /*******************/

  if (fsys != NULL)
  {
    printf(gettext("Loading and sending %s...\n"), fs_filename); 
    PDEBUG("Filesystem...\n");
    fseek(fsys, 0L, SEEK_SET);
    place = FS_ADDRESS;
    while ((len = fread(buf, 1, 64*MAX_DATA_MESSAGE_SIZE, fsys)) > 0)
    {
      PDEBUG(gettext("%ld bytes read from file %s\n"), len, fs_filename);
      if (send_block(adsl_handle, place, buf, len))
        return -1;
      place += len;
    } 
    fclose(fsys);
  }


  /*****************/
  /* send firmware */
  /*****************/

  printf(gettext("Loading and sending %s...\n"), fw_filename); 
  PDEBUG("Firmware...\n");
  fseek(soft, 0L, SEEK_SET);
  place = FW_ADDRESS;
  while ((len = fread(buf, 1, 64*MAX_DATA_MESSAGE_SIZE, soft)) > 0)
  {
    PDEBUG(gettext("%ld bytes read from file %s\n"), len, fw_filename);
    if (send_block(adsl_handle, place, buf, len))
      return -1;
    place += len;
  } 
  fclose(soft);


  /*************/
  /* post load */
  /*************/

  PDEBUG("PostInit...\n");

#if !defined( OLITECV3 ) && !defined( ETECHV2 )
  /* patch (boot rom) */
  if (send_block(adsl_handle, BR_ADDRESS, inifirm, sizeof(inifirm)))
    return -1;
#endif

  /* write vendorID and productID (boot signature) */
  if (write_value(adsl_handle, WB_SIGN_ADDRESS, (ID_PRODUCT1 << 16) | ID_VENDOR))
    return -1;

#if defined( OLITECV3 ) || defined( ETECHV2 )
  /* jump to firmware address */
  if (write_goto(adsl_handle, FW_ADDRESS))
    return -1;
#endif

#if !defined( OLITECV3 ) && !defined( ETECHV2 )
  /* patch return address (boot rom) */
  if (write_value(adsl_handle, BR_STACK_ADDRESS, BR_ADDRESS))
    return -1;
#endif

  printf(gettext("Firmware is sent!\n"));

  /* wait */
  sleep(1);


  /**************************************************/
  /* set parameters, get info, initialize ADSL line */
  /**************************************************/

#ifndef ETECHV2
  /* clear endpoints */
  clear_endpoints(adsl_handle, 2);
#endif

  /* start thread to read answers to commands sent */
  if (pthread_create(&dispatch_info_thread, NULL, dispatch_info, adsl_handle)) {
    printf(gettext("Error: I can't create dispatch_info_thread\n"));
    return -1;
  }

#ifndef ETECHV2
  /* write parameters */
  PDEBUG("Sending CMD_CARD_DATA_SET...\n");
  if (write_values(adsl_handle, CMD_CARD_DATA_SET, PARAM_FIRST, PARAM_LAST)) {
    return -1;
  }
  sleep(1);
#endif

  /* get information about modem */
#ifndef ETECHV2
  PDEBUG("Sending CMD_CARD_CONTROLLER_VERSION_GET...\n");
  if (write_zeros(adsl_handle, CMD_CARD_CONTROLLER_VERSION_GET))
    return -1;
  sleep(1);
  PDEBUG("Sending CMD_CARD_INFO_GET...\n");
  if (write_zeros(adsl_handle, CMD_CARD_INFO_GET))
    return -1;
  sleep(1);
#else
  PDEBUG("Sending CMD_CARD_GET_STATUS...\n");
  if (write_zeros(adsl_handle, CMD_CARD_GET_STATUS))
    return -1;
  sleep(1);
  PDEBUG("Sending CMD_CARD_GET_DATA_LINK_STATUS...\n");
  if (write_zeros(adsl_handle, CMD_CARD_GET_DATA_LINK_STATUS))
    return -1;
  sleep(1);
  PDEBUG("Sending CMD_CARD_PACKING_MODE...\n");
  if (command_value(adsl_handle, CMD_CARD_PACKING_MODE, 1))
    return -1;
  sleep(1);
  PDEBUG("Sending CMD_CARD_PACKING_MODE...\n");
  if (command_value(adsl_handle, CMD_CARD_PACKING_MODE, 1))
    return -1;
  sleep(1);
  PDEBUG("Sending CMD_CARD_GET_MAC_ADDRESS...\n");
  if (write_zeros(adsl_handle, CMD_CARD_GET_MAC_ADDRESS))
    return -1;
  sleep(1);
#endif
  
  printf("Firmware version = ");
  for (i = 0; i < sizeof(modem_info.firm_version); i++)
    printf("%c", modem_info.firm_version[i]);
  printf("\n");
  printf("MAC address = ");
  for (i = 0; i < sizeof(modem_info.mac)-1; i++)
    printf("%02x:", modem_info.mac[i] & 0xff);
  printf("%02x", modem_info.mac[sizeof(modem_info.mac)-1] & 0xff);
  printf("\n");
  printf("Data line status = %d\n", modem_info.line_status);

/*
  PDEBUG("Sending CMD_CARD_SERIAL_DATA_PATH_GET...\n");
  if (write_zeros(adsl_handle, CMD_CARD_SERIAL_DATA_PATH_GET))
    return -1;
*/
//  sleep(1);

  /* start line */
  printf("Starting ADSL line...\n");
  PDEBUG("Sending CMD_CHIP_ADSL_LINE_START...\n");
  if (write_zeros(adsl_handle, CMD_CHIP_ADSL_LINE_START)) {
    pthread_exit(vt);
    return -1;
  }
  sleep(4);

#ifndef  ETECHV2
  /* waiting until line is up (a maximum time) */
  printf(gettext("Waiting ADSL line is up (until %d seconds)...\n"), MAX_WAIT_LINE_UP);
  time(&first); before = first;
  do {
    PDEBUG("Sending CMD_CARD_INFO_GET...\n");
    if (write_zeros(adsl_handle, CMD_CARD_INFO_GET))
      return -1;
    PDEBUG(gettext("Line status = %02x\n"), modem_info.line_status);
    PDEBUG(gettext("ADSL status = %02x\n"), modem_info.adsl_status);
    if (difftime(time(&last), before) > 1) {
//#ifndef DEBUG
      printf(".");
      fflush(stdout);
//#endif
      before = last;
    }
  }
  while ((modem_info.adsl_status != ADSL_STATUS_UP) && (difftime(last, first) < MAX_WAIT_LINE_UP));
  printf("\n");

  if (modem_info.adsl_status == ADSL_STATUS_UP)
    printf(gettext("ADSL line is up (Downstream %u Kbits/s, Upstream %u Kbits/s)\n"), modem_info.down_bitrate, modem_info.up_bitrate);
  else
    printf(gettext("ADSL line is down\n"));
#endif

  time(&first); last = before = first;
#ifdef DEBUG
  printf(gettext("Waiting to receive first ATM cells...\n"));
  do {
    len = usb_bulk_read(adsl_handle, USB_IN_DATA, buf, sizeof(buf), DATA_TIMEOUT);
    if (len > 0)  {
      printf("\n");
      printf(gettext("ATM cells received:\n"));
      dump(buf, len, 16);
      return 0;
    }
    if (difftime(time(&last), before) > 1) {
      printf(".");
      fflush(stdout);
      before = last;
    }
  } 
  while (difftime(last, first) < 60);
  printf("\n");
#endif

  return 0;
}


int main(int argc, char *argv[])
{
  /* bus structures variables */
  struct usb_bus * bus;
  struct usb_device * dev;
  struct usb_device * adsl_dev = NULL;
  usb_dev_handle * adsl_handle;

  /* boolean value */
  int goon;

  /* result code */
  int r = 0; 


  /* init locale */
  setlocale(LC_ALL, "");
  //if (file_exists("./locale")) 
  //  bindtextdomain(TF_CODE, "./locale");  /* set directory for a domain (source code messages) */
  //else 
    bindtextdomain(TF_CODE, "/usr/share/locale");  /* set directory for a domain (source code messages) */
  textdomain(TF_CODE);  /* set domain */

  /* show program information */
  printf(gettext("Conexant AccessRunner microcode upload program."));
  printf(" 4/5/2003\n");
  printf("Josep Comas <jcomas@gna.es>\n");
  printf("Pascal Boucher <bcrp17@tiscali.fr>\n\n");

  /* check parameters */
  if (argc < 2)
  {
    printf(gettext("Usage:\n"));
    printf(gettext("   %s firmware_filename [filesystem_image]\n"), argv[0]);
    printf(gettext("\nExample:\n"));
    printf(gettext("   Load firmware: %s cxinit.bin\n"), argv[0]);
    return -1;
  }

  /* init USB bus and find devices */
  usb_init();
  if (usb_find_busses())
  {
    printf(gettext("Error: I can't find busses\n"));
    return -1;
  }
  if (usb_find_devices())
  {
    printf(gettext("Error: I can't find devices\n"));
    return -1;
  }

  printf(gettext("Compiled for ADSL modem with VendorID = %04x & ProductID = %04x\n"), ID_VENDOR, ID_PRODUCT1);

  /* search first Conexant AccessRunner */
  bus = usb_busses;
  goon = 1;
  while (bus && goon)
  {
    dev = bus->devices;
    while (dev && goon)
    {
      if (dev->descriptor.idVendor == ID_VENDOR &&
          dev->descriptor.idProduct == ID_PRODUCT1)
      {
        goon = 0;
        adsl_dev = dev;
      }
      if (goon)
        dev = dev->next;
    }
    bus = bus->next;
  }
  if (adsl_dev == NULL)
  {
    printf(gettext("Error: I didn't find ADSL modem\n"));
    return -1;
  }
  printf("ADSL modem found!\n");
/*
  printf(gettext("I found ADSL modem with VendorID = %04x & ProductID = %04x\n"),
         adsl_dev->descriptor.idVendor, adsl_dev->descriptor.idProduct);
*/
#if DEBUG
   printf(gettext(" bLength: 0x%02x\n"), adsl_dev->config->bLength);
   printf(gettext(" bDescriptorType: 0x%02x\n"), adsl_dev->config->bDescriptorType);
   printf(gettext(" wTotalLength: 0x%04x\n"), adsl_dev->config->wTotalLength);
   printf(gettext(" bNumInterfaces: 0x%02x\n"), adsl_dev->config->bNumInterfaces);
   printf(gettext(" bConfigurationValue: 0x%02x\n"), adsl_dev->config->bConfigurationValue);
   printf(gettext(" iConfiguration: 0x%02x\n"), adsl_dev->config->iConfiguration);
   printf(gettext(" bmAttributes: 0x%02x\n"), adsl_dev->config->bmAttributes);
   printf(gettext(" MaxPower: 0x%02x\n"), adsl_dev->config->MaxPower);
#endif

  /* connect to ADSL modem */
  adsl_handle = usb_open(adsl_dev);
  if (adsl_handle == NULL)
  {
    printf(gettext("Error: Couldn't get device handle for ADSL modem\n"));
    return -1;
  }
  /* set configuration */
  if (usb_set_configuration(adsl_handle, 1) < 0)
  {
    printf("Error: usb_set_configuration: %s\n", usb_strerror());
    return -1;
  }
  /* check if other program is using interface 0 */
  if (usb_claim_interface(adsl_handle, 0) < 0)
  {
    printf("Error: usb_claim_interface: %s\n", usb_strerror());
    return -1;
  }
  PDEBUG(gettext("Interface = %d\n"), adsl_handle->interface);

  r = load_firmware(adsl_handle, argv[1], argc > 2? argv[2] : NULL); 

  PDEBUG(gettext("Releasing interface...\n"));
  usb_release_interface(adsl_handle, 0);
  PDEBUG(gettext("Releasing device...\n"));
  usb_close(adsl_handle);
  printf("Done.\n");

  return r;
   
}

